home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Chat & Communication / Digsby build 37 / digsby_setup.exe / lib / social / twitter / twitter.pyo (.txt) < prev    next >
Python Compiled Bytecode  |  2008-10-13  |  30KB  |  974 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.5)
  3.  
  4. from social.network import SocialNetwork
  5. from util.threads.threadpool2 import threaded
  6. from threading import Lock
  7. from util.cacheable import cproperty
  8. from util.observe.observabledict import ObservableDict
  9. from common.actions import action
  10. from urllib2 import HTTPError
  11. from collections import defaultdict
  12. import common.notifications as common
  13. from common.Protocol import ProtocolStatus
  14. from protocols.api import adapt
  15. from util import dictdiff, dictsub, get_snurl
  16. from common.notifications import fire
  17. from protocols.adapters import AdaptationFailure
  18. from social.twitter.interfaces import ITwitterFetcher, IDisplayableTweet
  19. from social.twitter.functions import pack_statuses2, unpack_statuses2, pack_direct_messages2, unpack_direct_messages2, time_filter, direct_msg
  20. from social.twitter.constants import TAB_RELOAD_INTERVAL, TO_FIRE, ALL_TWEETS, TWITTER_HTTP
  21. import objects
  22. from common import pref
  23. import urllib2
  24. import simplejson
  25. import socket
  26. import httplib
  27. import os
  28. import wx
  29. import time
  30. import traceback
  31. from operator import attrgetter
  32. NORMAL_TWITTER_RATE = 100
  33. SPAM_MESSAGE = 'Using Digsby for IM, Email, and Twitter - http://www.digsby.com'
  34. TWEETS_KINDS_FOR_TAB = {
  35.     'timeline': ALL_TWEETS,
  36.     'replies': [
  37.         'friend_reply'],
  38.     'directs': [
  39.         'direct_recieved'],
  40.     'archive': [
  41.         'self_reply',
  42.         'self_tweet',
  43.         'direct_sent'],
  44.     'favorites': [
  45.         'self_reply',
  46.         'self_tweet',
  47.         'friend_reply',
  48.         'friend_tweet'] }
  49.  
  50. try:
  51.     from _speedups import adapt
  52. except ImportError:
  53.     import sys
  54.     print >>sys.stderr, 'WARNING: using slow protocols'
  55.  
  56. from logging import getLogger
  57. log = getLogger('twitter')
  58.  
  59. class TwitterkStatus(ProtocolStatus):
  60.     CHECKING = 'Checking...'
  61.  
  62.  
  63. class Twitter(SocialNetwork):
  64.     Statuses = TwitterkStatus
  65.     protocol = 'twitter'
  66.     service = protocol
  67.     HTTP = TWITTER_HTTP
  68.     url_cache = { }
  69.     updatefreq = 60
  70.     header_tabs = [
  71.         'timeline',
  72.         'replies',
  73.         'directs',
  74.         'archive',
  75.         'favorites']
  76.     timer_mapping = {
  77.         'friends_timeline': 'statuses/friends_timeline.json',
  78.         'direct_messages': 'direct_messages.json',
  79.         'replies': 'replies.json' }
  80.     filters = { }
  81.     
  82.     def __init__(self, *a, **kwargs):
  83.         self.auto_throttle = kwargs.get('auto_throttle', self.default('auto_throttle'))
  84.         self.do_spam = kwargs.get('do_spam', False)
  85.         self.do_follow_digsby = kwargs.get('do_follow_digsby', False)
  86.         self.name = kwargs['name']
  87.         self.update_lock = Lock()
  88.         self.header_funcs = [ (k, (lambda v = (v,): self.switch_to_tab(v)), False) for k, v in [
  89.             ('Timeline', 'timeline'),
  90.             ('Replies', 'replies'),
  91.             ('Directs', 'directs'),
  92.             ('Archive', 'archive'),
  93.             ('Favorites', 'favorites')] ]
  94.         self._current_tab = self.header_tabs[0]
  95.         self._dirty = ObservableDict()
  96.         self.timers = { }
  97.         self.update_times = { }
  98.         self.favorites_check_time = 0
  99.         self.sent_check_time = 0
  100.         self.have_succeeded = False
  101.         for name in self.timer_mapping:
  102.             self.timers[name] = kwargs.get(name, self.default(name))
  103.         
  104.         self.fetchers = dict((lambda .0: for key, val in .0:
  105. (key, ITwitterFetcher(val)))(self.timer_mapping.iteritems()))
  106.         self.marked_favorites = set()
  107.         self.clean_init = False
  108.         for tabname in self.header_tabs:
  109.             self._dirty[tabname] = True
  110.         
  111.         SocialNetwork.__init__(self, *a, **kwargs)
  112.  
  113.     
  114.     def onCreate(self):
  115.         if self.do_follow_digsby:
  116.             threaded(self.follow)(name = 'digsby', popups = False)
  117.         
  118.         if self.do_spam:
  119.             threaded(self.send_tweet)(SPAM_MESSAGE, popups = False)
  120.         
  121.  
  122.     
  123.     def follow(self, name, popups = True):
  124.         url = 'friendships/create/%s.json' % name
  125.         
  126.         try:
  127.             _json = self.api._FetchUrl(url, post_data = { })
  128.         except Exception:
  129.             e = None
  130.             traceback.print_exc()
  131.             
  132.             try:
  133.                 data = e.read()
  134.                 e.close()
  135.                 error_text = simplejson.loads(data)['error']
  136.                 if not error_text:
  137.                     raise 
  138.                 
  139.                 log.error('JSON error from twitter: %r', error_text)
  140.                 if popups:
  141.                     wx.CallAfter(wx.MessageBox, error_text, _('Twitter Error'))
  142.             except Exception:
  143.                 log.error('Non-JSON error while trying to send tweet: %r', e)
  144.                 if popups:
  145.                     wx.CallAfter(wx.MessageBox, _('Digsby encountered an error attempting to follow "%s".' % name), _('Twitter Error'))
  146.                 
  147.             except:
  148.                 popups
  149.  
  150.             return None
  151.  
  152.  
  153.     
  154.     def dirty(self):
  155.         return self._dirty[self._current_tab]
  156.  
  157.     dirty = property(dirty)
  158.     
  159.     def update_frequencies(self):
  160.         return self.timers.copy()
  161.  
  162.     update_frequencies = property(update_frequencies)
  163.     
  164.     def update_info(self, **info):
  165.         info.pop('do_spam', None)
  166.         info.pop('do_follow_digsby', None)
  167.         for name in self.timer_mapping:
  168.             if name in info or name not in self.timers:
  169.                 self.timers[name] = info.pop(name, self.default(name))
  170.                 continue
  171.         
  172.         
  173.         try:
  174.             del self._api
  175.         except AttributeError:
  176.             pass
  177.  
  178.         SocialNetwork.update_info(self, **info)
  179.  
  180.     
  181.     def get_options(self):
  182.         opts = SocialNetwork.get_options(self)
  183.         for k, v in self.timers.items():
  184.             if v != self.default(k):
  185.                 opts[k] = v
  186.                 continue
  187.         
  188.         if self.auto_throttle != self.default('auto_throttle'):
  189.             opts['auto_throttle'] = self.auto_throttle
  190.         
  191.         return opts
  192.  
  193.     tweets = cproperty(dict, box = pack_statuses2, unbox = unpack_statuses2, user = True)
  194.     direct_msgs = cproperty(dict, box = pack_direct_messages2, unbox = unpack_direct_messages2, user = True)
  195.     favorites_check_time = cproperty(0, user = True)
  196.     sent_check_time = cproperty(0, user = True)
  197.     last_update = cproperty(0, user = True)
  198.     
  199.     def switch_to_tab(self, tabname):
  200.         last = self._current_tab
  201.         if tabname != last:
  202.             self.setnotify('_current_tab', tabname)
  203.             self.tabloaded(tabname)
  204.         
  205.  
  206.     
  207.     def tabloaded2(self, tabname, force = False):
  208.         self.tabloaded(tabname, force)
  209.         return True
  210.  
  211.     
  212.     def tabloaded(self, tabname, force = False):
  213.         if force and tabname == 'timeline':
  214.             self.update_times.pop('friends_timeline', None)
  215.             self._update(forced = [
  216.                 'friends_timeline'])
  217.         elif force and tabname == 'replies':
  218.             self.update_times.pop('replies', None)
  219.             self._update(forced = [
  220.                 'replies'])
  221.         elif force and tabname == 'directs':
  222.             self.update_times.pop('direct_messages', None)
  223.             self._update(forced = [
  224.                 'direct_messages'])
  225.         elif tabname == 'archive':
  226.             if force or time.time() >= self.sent_check_time + TAB_RELOAD_INTERVAL:
  227.                 
  228.                 try:
  229.                     log.info('getting sent direct messages')
  230.                     fetcher = ITwitterFetcher('direct_messages/sent.json')
  231.                     sent = fetcher.Fetch(self.api)
  232.                     (new_tweets, updated_tweets) = self.new_data(sent)
  233.                     self.throw_mud(new_tweets + updated_tweets)
  234.                     self.update_tweets(new_tweets + updated_tweets)
  235.                     self.sent_check_time = time.time()
  236.                     log.info('%d new from sent', len(new_tweets))
  237.                 except (IOError, urllib2.HTTPError, socket.error, httplib.HTTPException):
  238.                     traceback.print_exc()
  239.                 except:
  240.                     None<EXCEPTION MATCH>(IOError, urllib2.HTTPError, socket.error, httplib.HTTPException)
  241.                 
  242.  
  243.             None<EXCEPTION MATCH>(IOError, urllib2.HTTPError, socket.error, httplib.HTTPException)
  244.         elif tabname == 'favorites':
  245.             if force or time.time() >= self.favorites_check_time + TAB_RELOAD_INTERVAL:
  246.                 
  247.                 try:
  248.                     log.info('getting sent direct messages')
  249.                     fetcher = ITwitterFetcher('favorites.json')
  250.                     favs = fetcher.Fetch(self.api)
  251.                     for status in favs:
  252.                         self.marked_favorites.add(status.id)
  253.                     
  254.                     (new_tweets, updated_tweets) = self.new_data(favs)
  255.                     self.throw_mud(new_tweets + updated_tweets)
  256.                     self.update_tweets(new_tweets + updated_tweets)
  257.                     self.favorites_check_time = time.time()
  258.                     log.info('%d new from favorites', len(new_tweets))
  259.                 except (IOError, urllib2.HTTPError, socket.error, httplib.HTTPException):
  260.                     traceback.print_exc()
  261.                 except:
  262.                     None<EXCEPTION MATCH>(IOError, urllib2.HTTPError, socket.error, httplib.HTTPException)
  263.                 
  264.  
  265.             None<EXCEPTION MATCH>(IOError, urllib2.HTTPError, socket.error, httplib.HTTPException)
  266.         
  267.  
  268.     tabloaded = threaded(tabloaded)
  269.     
  270.     def api(self):
  271.         
  272.         try:
  273.             return self._api
  274.         except AttributeError:
  275.             import twitterapi as twitterapi
  276.             self._api = twitterapi.TwitterApi(self)
  277.             self._api.source = 'digsby'
  278.             return self._api
  279.  
  280.  
  281.     api = property(api)
  282.     
  283.     def Connect(self):
  284.         self.on_connect()
  285.  
  286.     
  287.     def Disconnect(self):
  288.         self.change_state(self.Statuses.OFFLINE)
  289.  
  290.     
  291.     def observe_count(self, callback):
  292.         self.add_gui_observer(callback, '_current_tab')
  293.         self._dirty.add_gui_observer(callback)
  294.  
  295.     
  296.     def observe_state(self, callback):
  297.         self.add_gui_observer(callback, 'enabled')
  298.  
  299.     
  300.     def unobserve_count(self, callback):
  301.         self.remove_gui_observer(callback, '_current_tab')
  302.         self._dirty.remove_gui_observer(callback)
  303.  
  304.     
  305.     def unobserve_state(self, callback):
  306.         self.remove_gui_observer(callback)
  307.  
  308.     
  309.     def getFeed(self, kind):
  310.         display = []
  311.         if kind == 'self_feed':
  312.             kinds = ('self_tweet', 'self_reply')
  313.         else:
  314.             kinds = TWEETS_KINDS_FOR_TAB[kind]
  315.         for tweet in self.tweets.itervalues():
  316.             if tweet.kind in kinds:
  317.                 display.append(tweet)
  318.                 continue
  319.         
  320.         for tweet in self.direct_msgs.itervalues():
  321.             if tweet.kind in kinds:
  322.                 display.append(tweet)
  323.                 continue
  324.         
  325.         ret = time_filter(sorted(display, key = attrgetter('created_at_in_seconds'), reverse = True))
  326.         for val in ret:
  327.             val.twitter = self
  328.             val.do_cache(cache = self.url_cache)
  329.         
  330.         return ret
  331.  
  332.     
  333.     def self_feed(self):
  334.         return self.getFeed('self_feed')[:1]
  335.  
  336.     self_feed = property(self_feed)
  337.     
  338.     def timeline(self):
  339.         tl = self.getFeed('timeline')[:50]
  340.         if self.self_feed == tl[:1]:
  341.             tl = tl[1:]
  342.         
  343.         return tl
  344.  
  345.     timeline = property(timeline)
  346.     
  347.     def replies(self):
  348.         return self.getFeed('replies')[:50]
  349.  
  350.     replies = property(replies)
  351.     
  352.     def directs(self):
  353.         return self.getFeed('directs')[:50]
  354.  
  355.     directs = property(directs)
  356.     
  357.     def archive(self):
  358.         ar = self.getFeed('archive')[:50]
  359.         if self.self_feed == ar[:1]:
  360.             ar = ar[1:]
  361.         
  362.         return ar
  363.  
  364.     archive = property(archive)
  365.     
  366.     def favorites(self):
  367.         return _[1][:50]
  368.  
  369.     favorites = property(favorites)
  370.     
  371.     def isFavorite(self, status):
  372.         return status.id in self.marked_favorites
  373.  
  374.     
  375.     def statusShouldShow(self, tweet, kind):
  376.         if kind == 'create_favorite':
  377.             return not self.isFavorite(tweet)
  378.         elif kind == 'destroy_favorite':
  379.             return self.isFavorite(tweet)
  380.         elif kind == 'delete':
  381.             return tweet.user.screen_name.lower() == self.name.lower()
  382.         elif kind == 'reply' or kind == 'direct':
  383.             return tweet.user.screen_name.lower() != self.name.lower()
  384.         else:
  385.             return False
  386.  
  387.     
  388.     def directMessageShouldShow(self, tweet, kind):
  389.         if kind == 'direct':
  390.             return tweet.direct
  391.         elif kind == 'delete':
  392.             return tweet.sender.screen_name.lower() != self.name.lower()
  393.         
  394.  
  395.     
  396.     def shouldShow(self, tweet, kind):
  397.         if not tweet.direct:
  398.             return self.statusShouldShow(tweet, kind)
  399.         
  400.         if tweet.direct:
  401.             return self.directMessageShouldShow(tweet, kind)
  402.         
  403.         return False
  404.  
  405.     
  406.     def cleanup_iter(self, iterable):
  407.         out = []
  408.         for tweet in iterable:
  409.             
  410.             try:
  411.                 tweet = adapt(tweet, IDisplayableTweet)
  412.             except AdaptationFailure:
  413.                 log.error('there was an error adapting %r', tweet)
  414.                 continue
  415.  
  416.             tweet.twitter = self
  417.             tweet.do_cache(cache = self.url_cache)
  418.             out.append(tweet)
  419.         
  420.         return out
  421.  
  422.     
  423.     def new_data(self, l):
  424.         l = self.cleanup_iter(l)
  425.         tweets = { }
  426.         direct_msgs = { }
  427.         for tweet in l:
  428.             if tweet.direct:
  429.                 direct_msgs[tweet.id] = tweet
  430.                 continue
  431.             tweets[tweet.id] = tweet
  432.         
  433.         tweets = dictdiff(self.tweets, tweets)
  434.         new_tweets = dictsub(tweets, self.tweets)
  435.         updated_tweets = dictsub(tweets, new_tweets)
  436.         direct_msgs = dictdiff(self.direct_msgs, direct_msgs)
  437.         new_direct_msgs = dictsub(direct_msgs, self.direct_msgs)
  438.         updated_direct_msgs = dictsub(direct_msgs, new_direct_msgs)
  439.         return (new_tweets.values() + new_direct_msgs.values(), updated_tweets.values() + updated_direct_msgs.values())
  440.  
  441.     
  442.     def update_tweets(self, tweets):
  443.         for tweet in tweets:
  444.             if tweet.direct:
  445.                 self.direct_msgs[tweet.id] = tweet
  446.                 continue
  447.             self.tweets[tweet.id] = tweet
  448.         
  449.         self.direct_msgs = self.direct_msgs
  450.         self.tweets = self.tweets
  451.  
  452.     
  453.     def expire_tweets(self):
  454.         num_non_favs = defaultdict((lambda : 0))
  455.         tweets2 = { }
  456.         for k, v in sorted(self.tweets.iteritems(), key = (lambda obj: obj[1].created_at_in_seconds), reverse = True):
  457.             if v.id in self.marked_favorites:
  458.                 tweets2[k] = v
  459.                 continue
  460.             if num_non_favs[v.kind] < 55:
  461.                 tweets2[k] = v
  462.                 num_non_favs[v.kind] += 1
  463.                 continue
  464.         
  465.         self.tweets = tweets2
  466.         direct_msgs2 = { }
  467.         for k, v in sorted(self.direct_msgs.iteritems(), key = (lambda obj: obj[1].created_at_in_seconds), reverse = True):
  468.             if v.id in self.marked_favorites:
  469.                 direct_msgs2[k] = v
  470.                 continue
  471.             if num_non_favs[v.kind] < 55:
  472.                 direct_msgs2[k] = v
  473.                 num_non_favs[v.kind] += 1
  474.                 continue
  475.         
  476.         self.direct_msgs = direct_msgs2
  477.  
  478.     
  479.     def do_alerts(self, new_tweets):
  480.         seen_tweet_ids = set()
  481.         seen_direct_ids = set()
  482.         tweets = defaultdict(list)
  483.         for tweet in new_tweets:
  484.             if tweet.kind in TO_FIRE:
  485.                 ids = None if tweet.direct else seen_tweet_ids
  486.                 if tweet.id not in ids:
  487.                     ids.add(tweet.id)
  488.                     tweets[TO_FIRE[tweet.kind]].append(tweet)
  489.                 
  490.             tweet.id not in ids
  491.         
  492.         for topic, tweetitems in tweets.iteritems():
  493.             tweetitems = sorted(tweetitems, key = attrgetter('created_at_in_seconds'), reverse = True)
  494.             fire(topic, tweets = tweetitems, onclick = self.on_click, popupid = (self.name, topic))
  495.         
  496.  
  497.     
  498.     def throw_mud(self, tweets = None, favorites = False, all = False, **_k):
  499.         if all:
  500.             for tabname in TWEETS_KINDS_FOR_TAB.iterkeys():
  501.                 self._dirty[tabname] = True
  502.             
  503.             return None
  504.         
  505.         if favorites:
  506.             pass
  507.         
  508.         kinds_found = set()
  509.         for tweet in tweets:
  510.             kinds_found.add(tweet.kind)
  511.         
  512.         for tabname, kinds in TWEETS_KINDS_FOR_TAB.iteritems():
  513.             if favorites:
  514.                 if tabname != 'directs':
  515.                     self._dirty[tabname] = True
  516.                     continue
  517.                 
  518.             
  519.             if set(kinds).intersection(kinds_found):
  520.                 self._dirty[tabname] = True
  521.                 continue
  522.         
  523.  
  524.     
  525.     def muddy_status(self, status):
  526.         status = self.cleanup_iter([
  527.             status])[0]
  528.         if status.kind in ('self_tweet', 'self_reply'):
  529.             self.throw_mud(all = True)
  530.             return None
  531.         
  532.         self.throw_mud((status,))
  533.  
  534.     
  535.     def update(self):
  536.         self._update(forced = self.timers.keys())
  537.  
  538.     update = action((lambda self: if not pref('can_has_social_update', False):
  539. pass))(update)
  540.     
  541.     def reset_tweets(self):
  542.         self.tweets = { }
  543.         self.direct_msgs = { }
  544.         self.set_all_dirty()
  545.  
  546.     reset_tweets = action((lambda self: if not pref('can_has_social_update', False):
  547. pass))(reset_tweets)
  548.     
  549.     def set_all_dirty(self):
  550.         self.throw_mud(all = True)
  551.  
  552.     set_all_dirty = action((lambda self: if not pref('can_has_social_update', False):
  553. pass))(set_all_dirty)
  554.     
  555.     def reset_tab_times(self):
  556.         self.favorites_check_time = 0
  557.         self.sent_check_time = 0
  558.  
  559.     reset_tab_times = action((lambda self: if not pref('can_has_social_update', False):
  560. pass))(reset_tab_times)
  561.     
  562.     def update_now(self):
  563.         self._update()
  564.  
  565.     
  566.     def count(self):
  567.         pass
  568.  
  569.     count = property(count)
  570.     
  571.     def ensure_init(self):
  572.         if not self.clean_init:
  573.             self.change_state(self.Statuses.CONNECTING)
  574.             self.tweets = dict((lambda .0: for tweet in .0:
  575. (tweet.id, tweet))(self.cleanup_iter(self.tweets.values())))
  576.             self.direct_msgs = dict((lambda .0: for tweet in .0:
  577. (tweet.id, tweet))(self.cleanup_iter(self.direct_msgs.values())))
  578.             for tab in self.header_tabs:
  579.                 self._dirty[tab] = True
  580.             
  581.             self.clean_init = True
  582.         elif self.state is self.Statuses.OFFLINE:
  583.             self.change_state(self.Statuses.CONNECTING)
  584.         else:
  585.             self.change_state(self.Statuses.CHECKING)
  586.  
  587.     
  588.     def _update(self, forced = None):
  589.         if forced is None:
  590.             forced = []
  591.         
  592.         hourly_limit = NORMAL_TWITTER_RATE
  593.         if self.auto_throttle:
  594.             
  595.             try:
  596.                 hourly_limit = self._get_max_hourly()['hourly_limit']
  597.             except Exception:
  598.                 pass
  599.             except:
  600.                 None<EXCEPTION MATCH>Exception
  601.             
  602.  
  603.         None<EXCEPTION MATCH>Exception
  604.         if not max(float(hourly_limit), 0):
  605.             pass
  606.         throttle_by = float(NORMAL_TWITTER_RATE) / NORMAL_TWITTER_RATE
  607.         now = time.time()
  608.         self.ensure_init()
  609.         import gui.native.helpers as gui
  610.         if gui.native.helpers.GetUserIdleTime() > pref('twitter.idle_time', type = int, default = 600000):
  611.             return self.change_state(self.Statuses.ONLINE)
  612.         
  613.         results = { }
  614.         cont = False
  615.         
  616.         def do_routine(key, fetcher, results):
  617.             last_time = self.update_times.get(key, None)
  618.             if key in forced:
  619.                 log.info('%s update forced', key)
  620.             elif self.timers[key] == 0:
  621.                 log.info('%s update skipped, was 0', key)
  622.                 return None
  623.             elif last_time is None or now > (last_time - 1) + self.timers[key] * 60 * throttle_by:
  624.                 log.info('%s update triggered', key)
  625.             else:
  626.                 log.info("%s update skipped, hasn't been long enough", key)
  627.                 return None
  628.             
  629.             try:
  630.                 result = fetcher.Fetch(self.api)
  631.             except urllib2.HTTPError:
  632.                 e = None
  633.                 if int(e.code) == 400:
  634.                     self.update_times[key] = now
  635.                     return False
  636.                 elif int(e.code) == 401:
  637.                     return self.Reasons.BAD_PASSWORD
  638.                 else:
  639.                     return self.Reasons.CONN_LOST
  640.             except (IOError, urllib2.HTTPError, socket.error, httplib.HTTPException):
  641.                 return self.Reasons.CONN_LOST
  642.             except ValueError:
  643.                 return self.Reasons.CONN_LOST
  644.  
  645.             self.update_times[key] = now
  646.             (new_tweets, updated_tweets) = self.new_data(result)
  647.             results[fetcher.id] = (new_tweets, updated_tweets)
  648.             log.info('%d new from %r', len(new_tweets), fetcher.id)
  649.             return True
  650.  
  651.         did_succeed = False
  652.         for key, fetcher in self.fetchers.iteritems():
  653.             err_count = 0
  654.             for _i in xrange(3):
  655.                 ret = do_routine(key, fetcher, results)
  656.                 log.info('ret was %r', ret)
  657.                 if ret is False:
  658.                     err_count = -1
  659.                     break
  660.                 elif ret is self.Reasons.CONN_LOST:
  661.                     err_count += 1
  662.                 elif ret is self.Reasons.BAD_PASSWORD:
  663.                     self.set_offline(self.Reasons.BAD_PASSWORD)
  664.                     return None
  665.                 elif ret is True:
  666.                     did_succeed = True
  667.                     break
  668.                 elif ret is None:
  669.                     break
  670.                 
  671.                 if err_count < 3:
  672.                     time.sleep(2)
  673.                     continue
  674.             
  675.             if err_count in (-1,):
  676.                 break
  677.                 continue
  678.         else:
  679.             cont = True
  680.         new_tweets_utl = { }
  681.         updated_tweets_utl = { }
  682.         err_count = 0
  683.         for _i in xrange(3):
  684.             
  685.             try:
  686.                 tl_fetch_id = self.timer_mapping['friends_timeline']
  687.                 if cont and tl_fetch_id in results and not any((lambda .0: for tweet in .0:
  688. tweet.kind == 'self_tweet')(results[tl_fetch_id][0])) and len(results[tl_fetch_id][0]) >= 20:
  689.                     log.info('getting user timeline')
  690.                     fetcher = ITwitterFetcher('statuses/user_timeline.json')
  691.                     usertl = fetcher.Fetch(self.api)
  692.                     (new_tweets_utl, updated_tweets_utl) = self.new_data(usertl)
  693.                     results[fetcher.id] = (new_tweets_utl, updated_tweets_utl)
  694.                     log.info('%d new from user timeline', len(new_tweets_utl))
  695.                     did_succeed = True
  696.                 else:
  697.                     log.info("didn't need to get user timeline")
  698.             except urllib2.HTTPError:
  699.                 e = None
  700.                 if int(e.code) == 400:
  701.                     break
  702.                 elif int(e.code) == 401:
  703.                     self.set_offline(self.Reasons.BAD_PASSWORD)
  704.                     return None
  705.                 else:
  706.                     err_count += 1
  707.             except (IOError, socket.error, httplib.HTTPException):
  708.                 err_count += 1
  709.             except ValueError:
  710.                 err_count += 1
  711.  
  712.             if err_count < 3:
  713.                 time.sleep(2)
  714.                 continue
  715.         
  716.         if not did_succeed and not (self.have_succeeded):
  717.             self.set_offline(self.Reasons.CONN_FAIL)
  718.             return None
  719.         else:
  720.             self.have_succeeded = True
  721.         if not results:
  722.             self.change_state(self.Statuses.ONLINE)
  723.             self._dirty_error = True
  724.             return None
  725.         
  726.         to_alert = sum((lambda .0: for v in .0:
  727. v[0])(results.itervalues()), [])
  728.         newest = defaultdict((lambda : 0))
  729.         for tweet in self.tweets.values():
  730.             newest[tweet.kind] = max(newest[tweet.kind], tweet.created_at_in_seconds)
  731.         
  732.         for tweet in self.direct_msgs.values():
  733.             newest[tweet.kind] = max(newest[tweet.kind], tweet.created_at_in_seconds)
  734.         
  735.         to_alert = (filter,)((lambda tweet: newest[tweet.kind] < tweet.created_at_in_seconds), to_alert)
  736.         self.update_tweets(sum((lambda .0: for v in .0:
  737. for val in v:
  738. val)(results.itervalues()), []))
  739.         self.throw_mud(sum((lambda .0: for v in .0:
  740. v[0])(results.itervalues()), []))
  741.         self.update_times = self.update_times
  742.         self.do_alerts(to_alert)
  743.         self.expire_tweets()
  744.         if self.state != self.Statuses.OFFLINE:
  745.             self.change_state(self.Statuses.ONLINE)
  746.             self._dirty_error = True
  747.         
  748.  
  749.     _update = threaded(_update)
  750.     
  751.     def cache_dir(self):
  752.         return os.path.join('twitter', self.name)
  753.  
  754.     cache_dir = property(cache_dir)
  755.     
  756.     def cache_path(self):
  757.         return os.path.join(self.cache_dir, 'twitter.dat')
  758.  
  759.     cache_path = property(cache_path)
  760.     
  761.     def find_tweet(self, id):
  762.         
  763.         try:
  764.             return self.tweets[id]
  765.         except AttributeError:
  766.             return self.direct_msgs[id]
  767.  
  768.  
  769.     
  770.     def del_tweet(self, tweet):
  771.         if not (tweet.direct) and self.tweets.pop(tweet.id, None) is not None:
  772.             self.tweets = self.tweets
  773.         
  774.         if tweet.direct and self.direct_msgs.pop(tweet.id, None) is not None:
  775.             self.direct_msgs = self.direct_msgs
  776.         
  777.  
  778.     
  779.     def on_click(self, tweet):
  780.         if not tweet.direct:
  781.             wx.LaunchDefaultBrowser(self.HTTP + '%s/statuses/%d' % (tweet.user.screen_name, tweet.id))
  782.         
  783.         if tweet.direct:
  784.             return self.new_tweet_for_obj(tweet)
  785.         
  786.  
  787.     
  788.     def new_tweet_for_obj(self, tweet):
  789.         if not tweet.direct:
  790.             return self.new_tweet('@' + tweet.user.screen_name + ' ')
  791.         
  792.         if tweet.direct:
  793.             return self.new_tweet('d ' + tweet.sender.screen_name + ' ')
  794.         
  795.  
  796.     
  797.     def new_tweet(self, prefix = ''):
  798.         TwitterStatusDialog = TwitterStatusDialog
  799.         import social.twitter.twittergui
  800.         TwitterStatusDialog.ShowSetStatus(None, self.name, ok = (threaded,)((lambda info: self.send_tweet(**info))), initial_text = prefix, tiny_url = get_snurl)
  801.  
  802.     new_tweet = action((lambda self: if not self.state == self.Statuses.ONLINE:
  803. pass))(new_tweet)
  804.     OnClickHomeURL = OpenHomeURL = new_tweet
  805.     
  806.     def send_tweet(self, message, popups = True, **k):
  807.         if not message:
  808.             return None
  809.         
  810.         if isinstance(message, unicode):
  811.             message = message.encode('utf-8')
  812.         
  813.         dm = direct_msg.match(message)
  814.         if dm is not None:
  815.             post_message = self.api.PostDirectMessageData
  816.             post_args = dm.groups()
  817.         else:
  818.             post_message = self.api.PostUpdateData
  819.             post_args = (message,)
  820.         
  821.         try:
  822.             sent_msg = post_message(*post_args)
  823.         except Exception:
  824.             e = None
  825.             traceback.print_exc()
  826.             
  827.             try:
  828.                 data = e.read()
  829.                 e.close()
  830.                 error_text = simplejson.loads(data)['error']
  831.                 if not error_text:
  832.                     raise 
  833.                 
  834.                 log.error('JSON error from twitter: %r', error_text)
  835.                 if popups:
  836.                     wx.CallAfter(wx.MessageBox, error_text, _('Twitter Error'))
  837.             except Exception:
  838.                 traceback.print_exc()
  839.                 log.error('Non-JSON error while trying to send tweet: %r', e)
  840.                 if popups:
  841.                     wx.CallAfter(wx.MessageBox, _('Digsby encountered an error sending the message.'), _('Twitter Error'))
  842.                 
  843.             except:
  844.                 popups
  845.  
  846.             if popups:
  847.                 wx.CallAfter(self.new_tweet, message)
  848.             
  849.             return None
  850.  
  851.         (new_tweets, updated_tweets) = self.new_data([
  852.             sent_msg])
  853.         self.update_tweets(new_tweets + updated_tweets)
  854.         self.throw_mud(all = True)
  855.  
  856.     
  857.     def destroy_direct_message(self, id):
  858.         self._destroy_direct_message(id)
  859.         return True
  860.  
  861.     
  862.     def _destroy_direct_message(self, id):
  863.         dm = self.find_tweet(int(id))
  864.         url = 'direct_messages/destroy/%s.json' % dm.id
  865.         
  866.         try:
  867.             self.api._FetchUrl(url, { })
  868.         except HTTPError:
  869.             e = None
  870.             if int(e.code) != 404:
  871.                 traceback.print_exc()
  872.                 wx.CallAfter(wx.MessageBox, 'Digsby encountered an error deleting the direct message.')
  873.                 return None
  874.             
  875.         except Exception:
  876.             traceback.print_exc()
  877.             wx.CallAfter(wx.MessageBox, 'Digsby encountered an error deleting the direct message.')
  878.             return None
  879.  
  880.         self.del_tweet(dm)
  881.         self.muddy_status(dm)
  882.  
  883.     _destroy_direct_message = threaded(_destroy_direct_message)
  884.     
  885.     def destroy_status(self, id):
  886.         self._destroy_status(id)
  887.         return True
  888.  
  889.     
  890.     def _destroy_status(self, id):
  891.         status = self.find_tweet(int(id))
  892.         url = 'statuses/destroy/%s.json' % status.id
  893.         
  894.         try:
  895.             self.api._FetchUrl(url, { })
  896.         except HTTPError:
  897.             e = None
  898.             if int(e.code) != 404:
  899.                 traceback.print_exc()
  900.                 wx.CallAfter(wx.MessageBox, 'Digsby encountered an error deleting the status message.')
  901.                 return None
  902.             
  903.         except Exception:
  904.             traceback.print_exc()
  905.             wx.CallAfter(wx.MessageBox, 'Digsby encountered an error deleting the status message.')
  906.             return None
  907.  
  908.         self.del_tweet(status)
  909.         self.muddy_status(status)
  910.  
  911.     _destroy_status = threaded(_destroy_status)
  912.     
  913.     def create_favorite(self, id):
  914.         self._create_favorite(id)
  915.         return True
  916.  
  917.     
  918.     def _create_favorite(self, id):
  919.         status = self.find_tweet(int(id))
  920.         url = 'favorites/create/%s.json' % status.id
  921.         
  922.         try:
  923.             self.api._FetchUrl(url, { })
  924.         except HTTPError:
  925.             e = None
  926.             if int(e.code) != 404:
  927.                 traceback.print_exc()
  928.                 wx.CallAfter(wx.MessageBox, 'Digsby encountered an error creating the favorite.')
  929.                 return None
  930.             
  931.         except Exception:
  932.             traceback.print_exc()
  933.             wx.CallAfter(wx.MessageBox, 'Digsby encountered an error creating the favorite.')
  934.             return None
  935.  
  936.         self.marked_favorites.add(status.id)
  937.         self.muddy_status(status)
  938.  
  939.     _create_favorite = threaded(_create_favorite)
  940.     
  941.     def destroy_favorite(self, id):
  942.         self._destroy_favorite(id)
  943.         return True
  944.  
  945.     
  946.     def _destroy_favorite(self, id):
  947.         status = self.find_tweet(int(id))
  948.         url = 'favorites/destroy/%s.json' % status.id
  949.         
  950.         try:
  951.             self.api._FetchUrl(url, { })
  952.         except HTTPError:
  953.             e = None
  954.             if int(e.code) != 404:
  955.                 traceback.print_exc()
  956.                 wx.CallAfter(wx.MessageBox, 'Digsby encountered an error removing the favorite.')
  957.                 return None
  958.             
  959.         except Exception:
  960.             traceback.print_exc()
  961.             wx.CallAfter(wx.MessageBox, 'Digsby encountered an error removing the favorite.')
  962.             return None
  963.  
  964.         self.marked_favorites.remove(status.id)
  965.         self.muddy_status(status)
  966.  
  967.     _destroy_favorite = threaded(_destroy_favorite)
  968.     
  969.     def _get_max_hourly(self):
  970.         return simplejson.loads(self.api._FetchUrl('account/rate_limit_status.json'))
  971.  
  972.     get_max_hourly = threaded(_get_max_hourly)
  973.  
  974.